import { DocContainerType } from ".";
import wordCells from "../cells";
import { classNameDeal, dom, RecordOperation, getNodeInfo, removeItemsInArray, dealNodeClass, throttle } from "../tool";


const selectClassName = "gkh-selected";
const containerClassName = "gkh-doc-container";
const focusClassName = "gkh-focus";
const findChildNode = (node: any) => {
    let n: any = node.parentNode;
    let m: any = node;
    if (node.className && node.className.indexOf(containerClassName) > -1) {
        return null;
    }
    for (let i = 0; i < 20; i += 1) {
        if (n) {
            if (n.className && n.className.indexOf(containerClassName) > -1) {
                return m;
            }
            m = n;
            n = n.parentNode;
        } else {
            break;
        }
    }
    return null;
}
function DocContainer(this: DocContainerType, node: HTMLElement, mode: 1 | 2 | 3 | 4, parentContainer: any) {
    this.name = "docContainer";
    this.type = 2;
    this.handleCellType = 'add';
    const handleRecord = new RecordOperation(20);
    this.record = handleRecord;
    this.parentContainer = parentContainer;
    let selectBox: HTMLElement; // 选择框
    let cellInfos: any[] = [];  // 单元信息 子节点信息
    let groupInfos: any = {};
    let groupInstances: any = {};
    let selectedCells: any[] = [];  // 选中的单元
    let selectedCellsBack: any[] = [];
    let cellIdIndex = 0;
    let containerInfo: any = {};
    let hasChange: boolean = false;
    let wordWidth: number = 0;
    let hasMouseDown: boolean = false;
    let downPoint: any = {};
    let focusCell: any;
    let changeCell: any;
    let handle = 0; // 1：改变盒子宽度向右 2：改变盒子高度向下 3：改变盒子宽度向左 4：改变盒子高度向上 5: 移动
    const precision = 5;
    let round: any = {};
    let parentNodeInfo: any = {};
    let positionPoint: any = {};
    const container = dom("div", { className: containerClassName, target: node });
    if (mode == 1 || mode == 4) {
        selectBox = dom("div", {
            target: container,
            style: "display: none; position: absolute; border: 1px dashed #18ff00;"
        });
        container.ondragover = function (event: DragEvent) {
            event.preventDefault();
            event.stopPropagation();
            event.cancelBubble = true;
            return false;
        }
        container.ondrop = (event: any) => {
            event.preventDefault();
            var data = event.dataTransfer.getData("gkhWordCellId");
            event.dataTransfer.setData("gkhWordCellId", undefined);
            if (data) {
                if (this.handleCellType == 'add') {
                    addItemByAttrib({ type: parseInt(data) });
                } else {
                    this.replaceItemBySelect({ type: parseInt(data) });
                }
            }
            event.stopPropagation();
            event.cancelBubble = true;
            return false;
        }
    }

    const wordItemStyle = (item: any) => {
        let w = mode === 1 || item[0].x || item[0].y ? (item[1] * wordWidth).toString().concat('px') : (item[1] * 100).toString().concat('%');
        return {
            width: w,
            minHeight: item[2].toString().concat('px'),
            borderLeft: item[3],
            borderTop: item[4],
            borderRight: item[5],
            borderBottom: item[6],
            backgroundColor: item[7],
            color: item[8],
            fontFamily: item[9],
            fontSize: item[10],
            fontWeight: item[11],
            textAlign: item[12],
            zIndex: item[13],
            margin: item[14],
            padding: item[15],
            position: item[16],
            left: item[17],
            top: item[18]
        }
    }

    function getWordItemData(item: any) {
        const { node, instance, info } = item;
        let style = instance.style;
        if (!style) {
            style = window.getComputedStyle(node);
        }
        const { borderLeft, borderTop, borderRight, borderBottom, backgroundColor, color, fontFamily, fontSize, fontWeight, textAlign, zIndex, margin, padding, position, left, top } = style;
        const family = fontFamily.length < 20 ? fontFamily : undefined;
        let nodeHeight = info.height;
        if (instance.attrData.type == 3) { // 表格减掉编辑头的高度
            nodeHeight -= 28;
        }
        let borderL: number | string = 0;
        if (borderLeft.indexOf("0px ") == -1 && borderLeft.indexOf("none ") == -1) {
            borderL = borderLeft;
        }
        let borderT: number | string = 0;
        if (borderTop.indexOf("0px ") == -1 && borderTop.indexOf("none ") == -1) {
            borderT = borderTop;
        }
        let borderR: number | string = 0;
        if (borderRight.indexOf("0px ") == -1 && borderRight.indexOf("none ") == -1) {
            borderR = borderLeft;
        }
        let borderB: number | string = 0;
        if (borderBottom.indexOf("0px ") == -1 && borderBottom.indexOf("none ") == -1) {
            borderB = borderBottom;
        }
        return [instance.getAttrData(), (info.width / wordWidth).toFixed(4), nodeHeight, borderL, borderT, borderR, borderB, backgroundColor, color, family, fontSize, fontWeight, textAlign, zIndex, margin, padding, position, left, top];
    }
    // 获取文档模板数据
    const getTempData = (): any => {
        const { borderWidth, borderLeft, borderTop, borderRight, borderBottom, backgroundColor, color, fontFamily, fontSize, fontWeight, margin, padding, paddingLeft, paddingRight } = window.getComputedStyle(container);
        const { width, height } = getNodeInfo(container);
        let family = fontFamily.length < 20 ? fontFamily : undefined;
        wordWidth = width - 2 * parseInt(borderWidth) - parseInt(paddingLeft) - parseInt(paddingRight);
        let borderL: number | string = 0;
        if (borderLeft.indexOf("0px ") == -1 && borderLeft.indexOf("none ") == -1) {
            borderL = borderLeft;
        }
        let borderT: number | string = 0;
        if (borderTop.indexOf("0px ") == -1 && borderTop.indexOf("none ") == -1) {
            borderT = borderTop;
        }
        let borderR: number | string = 0;
        if (borderRight.indexOf("0px ") == -1 && borderRight.indexOf("none ") == -1) {
            borderR = borderLeft;
        }
        let borderB: number | string = 0;
        if (borderBottom.indexOf("0px ") == -1 && borderBottom.indexOf("none ") == -1) {
            borderB = borderBottom;
        }
        return [
            [height, borderL, borderT, borderR, borderB, backgroundColor, color, family, fontSize, fontWeight, margin, padding],
            cellInfos.map(getWordItemData)
        ];
    }
    const renderTemplateData = (data: any) => {
        container.innerHTML = "";
        cellInfos = [];
        groupInfos = {};
        groupInstances = {};
        let temp: Array<any>;
        if (data instanceof Array && data.length > 1) {
            temp = data[0];
            container.style.minHeight = temp[0].toString().concat('px');
            container.style.borderLeft = temp[1];
            container.style.borderTop = temp[2];
            container.style.borderRight = temp[3];
            container.style.borderBottom = temp[4];
            container.style.backgroundColor = temp[5];
            container.style.color = temp[6];
            container.style.fontFamily = temp[7];
            container.style.fontSize = temp[8];
            container.style.fontWeight = temp[9];
            container.style.margin = temp[10];
            container.style.padding = temp[11];
            const { borderWidth, paddingLeft, paddingRight, width } = window.getComputedStyle(container);
            wordWidth = parseInt(width) - 2 * parseInt(borderWidth) - parseInt(paddingLeft) - parseInt(paddingRight);
            temp = data[1];
            if (temp instanceof Array) {
                temp.forEach(function (item) {
                    addItemByAttrib(item[0], wordItemStyle(item));
                });
            }
            itemChange();
        }
    }
    this.getTemplateData = getTempData;
    this.renderTemplate = renderTemplateData;
    const itemChange = (arg?: boolean) => {
        hasChange = false;
        containerInfo = getNodeInfo(container);
        cellInfos.forEach((item: any) => {
            if (item.instance.getNodeInfo) {
                item.info = item.instance.getNodeInfo();
            } else {
                item.info = getNodeInfo(item.node);
            }
            if (item.instance.sizeChange) {
                item.instance.sizeChange();
            }
        })
        if (arg != false) {
            const data = getTempData();
            handleRecord.push(data);
        }
    }

    this.cellChange = itemChange;
    const addItemByAttrib = (attrData: any, style?: any, value?: any, insertBeforeNode?: HTMLElement, groupIndex?: any) => {
        const { fieldName, groupName } = attrData || {};
        if (!fieldName) {
            attrData.fieldName = "field" + String(cellIdIndex++);
        }
        const cellItem = wordCells.getComponent(this, mode, attrData, style);
        const childNode = cellItem.render();
        if (!childNode.setStyle) {
            if (style instanceof Object) {
                for (var key in style) {
                    childNode.style[key] = style[key];
                }
            }
        }
        const cellInfo = {
            id: cellIdIndex++,
            attrData,
            instance: cellItem,
            node: childNode,
            info: undefined
        };
        childNode.cellInfo = cellInfo;
        if (insertBeforeNode) {
            container.insertBefore(childNode, insertBeforeNode);
            const newArr: Array<any> = new Array(cellInfos.length + 1);
            let j = 0;
            const iNode: any = insertBeforeNode;
            for (let i = 0; i < cellInfos.length; i++) {
                if (cellInfos[i].node == iNode) {
                    newArr[j++] = cellInfo;
                }
                newArr[j++] = cellInfos[i];
            }
            cellInfos = newArr;
        } else {
            container.appendChild(childNode);
            cellInfos.push(cellInfo);
        }
        if (cellItem.setValue && value != undefined && value != null) {
            cellItem.setValue(value);
        }
        if (cellItem.renderAfter) {
            cellItem.renderAfter();
        }
        if (groupName) {
            if (groupInfos[groupName]) {
                groupInfos[groupName][groupIndex].push(cellInfo);
                groupInstances[groupName][groupIndex].push(cellItem);
            } else {
                groupInfos[groupName] = [[cellInfo]];
                groupInstances[groupName] = [[cellItem]];
            }
        }
        itemChange();
    }

    const clearSelectedCells = () => {
        let className;
        selectedCells.forEach(item => {
            className = item.node.getAttribute('class') || "";
            item.node.setAttribute('class', classNameDeal(className, selectClassName, 'del'));
        });
        if (focusCell) {
            className = focusCell.node.getAttribute('class') || "";
            focusCell.node.setAttribute("class", classNameDeal(className, focusClassName, "del"));
        }
        selectedCellsBack = selectedCells;
        selectedCells = [];
    }

    const onMouseDown = (event: MouseEvent) => {
        hasMouseDown = true;
        const { screenX, screenY } = window;
        downPoint = {
            x: event.clientX + screenX,
            y: event.clientY + screenY
        };
        clearSelectedCells();
        const node = findChildNode(event.target);
        focusCell = null;
        if (node && node.cellInfo) {
            focusCell = node.cellInfo;
            selectedCellsBack = selectedCells;
            selectedCells = [node.cellInfo];
            const className = node.getAttribute('class') || "";
            node.setAttribute('class', classNameDeal(className, focusClassName, "add"));
        }
        if (selectBox) {
            selectBox.style.display = "block";
        }
        if (changeCell) {
            parentNodeInfo = getNodeInfo(container);
            const { left, right, top, bottom, width, height } = parentNodeInfo;
            const pleft = node.style.left ? parseFloat(node.style.left) : 0;
            const ptop = node.style.top ? parseFloat(node.style.top) : 0;
            positionPoint = {
                x: changeCell.info.x - pleft,
                y: changeCell.info.y - ptop,
            }
            round = {
                minX: left - positionPoint.x,
                maxX: left - positionPoint.x + width - changeCell.info.width,
                minY: top - positionPoint.y,
                maxY: top - positionPoint.y + height - changeCell.info.height
            }
            if (changeCell.instance.mouseDown) {
                changeCell.instance.mouseDown(downPoint.x, downPoint.y);
            } else if (handle > 4) {
                node.style.position = 'absolute';
            }
        }
    }
    const onMouseMove = throttle((event: MouseEvent) => {
        const { screenX, screenY } = window;
        const clientX = screenX + event.clientX;
        const clientY = screenY + event.clientY;
        if (hasMouseDown) {
            if (changeCell) {
                const { node, info: { width, height, x, y } } = changeCell;
                hasChange = true;
                if (changeCell.instance.mouseMove) {
                    changeCell.instance.mouseMove(clientX, clientY, handle);
                } else {
                    switch (handle) {
                        case 1: {
                            const dw = width + clientX - downPoint.x;
                            if (dw > 0 && dw < parentNodeInfo.width) {
                                node.style.width = dw.toString().concat('px');
                            }
                        } break;
                        case 2: {
                            const dh = height + clientY - downPoint.y;
                            if (dh > 0 && dh < parentNodeInfo.height) {
                                node.style.minHeight = dh.toString().concat('px');
                            }
                        } break;
                        case 3: {
                            const dx = x - parentNodeInfo.x + clientX - downPoint.x;
                            const dw = width + downPoint.x - clientX;
                            if (dw > 0 && dw < parentNodeInfo.width) {
                                node.style.left = dx.toString().concat('px');
                                node.style.width = dw.toString().concat('px');
                            }
                        } break;
                        case 4: {
                            const dy = y - parentNodeInfo.y + clientY - downPoint.y;
                            const dh = height + downPoint.y - clientY;
                            if (dh > 0 && dh < parentNodeInfo.height) {
                                node.style.top = dy.toString().concat('px');
                                node.style.minHeight = dh.toString().concat('px');
                            }
                        } break;
                        case 5: {
                            const dx = clientX - positionPoint.x;
                            const dy = clientY - positionPoint.y;
                            if (round.minX < dx && dx < round.maxX && round.minY < dy && dy < round.maxY) {
                                node.style.left = dx.toString().concat('px');
                                node.style.top = dy.toString().concat('px');
                            }
                        } break;
                        case 6: {
                            const dx = clientX - width - positionPoint.x;
                            const dy = clientY - positionPoint.y;
                            if (round.minX < dx && dx < round.maxX && round.minY < dy && dy < round.maxY) {
                                node.style.left = dx.toString().concat('px');
                                node.style.top = dy.toString().concat('px');
                            }
                        } break;
                        case 7: {
                            const dx = clientX - positionPoint.x;
                            const dy = clientY - height - positionPoint.y;
                            if (round.minX < dx && dx < round.maxX && round.minY < dy && dy < round.maxY) {
                                node.style.left = dx.toString().concat('px');
                                node.style.top = dy.toString().concat('px');
                            }
                        } break;
                        case 8: {
                            const dx = clientX - width - positionPoint.x;
                            const dy = clientY - height - positionPoint.y;
                            if (round.minX < dx && dx < round.maxX && round.minY < dy && dy < round.maxY) {
                                node.style.left = dx.toString().concat('px');
                                node.style.top = dy.toString().concat('px');
                            }
                        } break;
                    }
                }
            } else {
                clearSelectedCells();
                let pleft = 0;
                let ptop = 0;
                let pright = 0;
                let pbottom = 0;
                if (clientX - downPoint.x > 0) {
                    pleft = downPoint.x;
                    pright = clientX;
                } else {
                    pleft = clientX;
                    pright = downPoint.x;
                }
                if (clientY - downPoint.y > 0) {
                    ptop = downPoint.y;
                    pbottom = clientY;
                } else {
                    ptop = clientY;
                    pbottom = downPoint.y;
                }
                selectBox.style.left = (pleft - containerInfo.left).toString().concat('px');
                selectBox.style.top = (ptop - containerInfo.top).toString().concat('px');
                selectBox.style.width = (pright - pleft).toString().concat('px');
                selectBox.style.height = (pbottom - ptop).toString().concat('px');
                let className;
                cellInfos.forEach(item => {
                    const { left, top, right, bottom } = item.info;
                    if (right > pleft && bottom > ptop && left < pright && top < pbottom) {
                        className = item.node.getAttribute('class') || "";
                        item.node.setAttribute('class', classNameDeal(className, selectClassName, "add"));
                        selectedCells.push(item);
                    }
                });
                selectedCellsBack = selectedCells;
            }
        } else {
            const target = findChildNode(event.target);
            changeCell = undefined;
            let className = container.getAttribute('class') || '';
            className = className.replace(' gkh-box-move', '');
            className = className.replace(' gkh-box-eresize', '');
            className = className.replace(' gkh-box-nresize', '');
            if (target && target.cellInfo) {
                changeCell = target.cellInfo;
                handle = 0;
                const { left, right, top, bottom } = changeCell.info;
                // 触发移动标识
                if (clientX - left < precision && clientX > left && clientY > top && clientY - top < precision) {
                    className = className.concat(' gkh-box-move');  // 左上角移动
                    handle = 5;
                } else if (right - clientX < precision && clientX < right && clientY > top && clientY - top < precision) {
                    className = className.concat(' gkh-box-move'); // 右上角移动
                    handle = 6;
                } else if (clientX - left < precision && clientX > left && clientY < bottom && bottom - clientY < precision) {
                    className = className.concat(' gkh-box-move');  // 左下角移动
                    handle = 7;
                } else if (right - clientX < precision && clientX < right && clientY < bottom && bottom - clientY < precision) {
                    className = className.concat(' gkh-box-move');  // 右下角移动
                    handle = 8;
                } else if (right - clientX < precision && right > clientX) { // 触发宽度修改标识 向右
                    className = className.concat(' gkh-box-eresize');
                    handle = 1;
                } else if (bottom - clientY < precision && bottom > clientY) { // 触发高度修改标识 向下
                    className = className.concat(' gkh-box-nresize');
                    handle = 2;
                } else if (clientX - left < precision && left < clientX) { // 触发宽度修改标识 向左
                    className = className.concat(' gkh-box-eresize');
                    handle = 3;
                } else if (clientY - top < precision && top < clientY) { // 触发高度修改标识 向上
                    className = className.concat(' gkh-box-nresize');
                    handle = 4;
                }
            }
            container.setAttribute('class', className);
        }
    }, 10);

    const onMouseUp = () => {
        if (hasMouseDown && this.selectedCellChange) {
            if (focusCell) {
                let style = focusCell.instance.style;
                if (!style) {
                    style = window.getComputedStyle(focusCell.node);
                }
                this.selectedCellChange(style, focusCell.instance, this);
            } else {
                this.selectedCellChange(window.getComputedStyle(container), undefined, this);
            }
        }
        hasMouseDown = false;
        if (changeCell) {
            changeCell.instance.mouseUp?.();
        }
        if (selectBox) {
            selectBox.style.display = 'none';
            selectBox.style.width = '0';
            selectBox.style.height = '0';
        }
        if (hasChange) {
            itemChange();
        }
    }

    container.addEventListener("mousedown", onMouseDown);
    if (mode == 1 || mode == 4) {
        container.addEventListener("mousemove", onMouseMove);
        container.addEventListener("mouseup", onMouseUp);
    }

    const getTemplate = () => {
        const { borderWidth, borderStyle, borderColor, backgroundColor, color, fontFamily, fontSize, lineHeight, fontWeight, margin, padding, paddingLeft, paddingRight } = window.getComputedStyle(container);
        const { width, height } = getNodeInfo(container);
        let family = fontFamily.length < 20 ? fontFamily : undefined;
        wordWidth = width - 2 * parseInt(borderWidth) - parseInt(paddingLeft) - parseInt(paddingRight);
        return [
            [height, borderWidth, borderStyle, borderColor, backgroundColor, color, family, fontSize, lineHeight, fontWeight, margin, padding],
            cellInfos.map(getWordItemData)
        ];
    }

    this.setCellStyle = (style: any) => {
        if (style instanceof Object) {
            if (selectedCells.length > 0) {
                selectedCells.forEach(({ node, instance }: any) => {
                    if (instance.setStyle) {
                        instance.setStyle(style);
                    } else {
                        for (var key in style) {
                            node.style[key] = style[key];
                        }
                    }
                })
            } else {
                for (var key in style) {
                    container.style[key] = style[key];
                }
            }
            itemChange();
        }
    }

    this.removeItemBySelect = () => {
        cellInfos = removeItemsInArray(cellInfos, selectedCells, "id", (item: any) => {
            container.removeChild(item.node);
        });
        itemChange();
    }

    this.replaceItemBySelect = (attrib) => {
        if (selectedCells.length > 0) {
            const insertNode = selectedCells[selectedCells.length - 1].node.nextSibling;
            addItemByAttrib(attrib, undefined, undefined, insertNode);
            this.removeItemBySelect();
        }
    }/**
     * 高亮组成员单元格 
     * @param {*} groupCells 数组，通过getSelectedCells获取的数据
     */
    this.showGroupCells = (groupCells) => {
        if (groupCells) {
            clearSelectedCells();
            selectedCells = groupCells;
            selectedCells.forEach(({ node }) => {
                dealNodeClass(node, selectClassName, true);
            });
        }
    }
    /**
     * 获取传入单元格，获取重新渲染所需数据
     * @param {*} groupCells 单元格，数组，通过getSelectedCells获取
     * @param {*} groupName 组名
     * @returns 
     */
    this.getGroupCellsInfo = (groupCells, groupName) => {
        return groupCells.map(item => {
            item.instance.attrData.groupName = groupName;
            return getWordItemData(item);
        })
    }
    this.getSelectedCells = () => {
        return selectedCellsBack;
    }

    /**
     * 根据组名自动查出这一组当中最后一行，并在最后一行添加新的组成员
     * @param {*} groupName 
     * @param {*} addCellInfos 
     * @param {*} value 给新增的单元格赋值 
     */
    this.addCellsByGroupName = (groupName, addCellInfos, value) => {
        const groupArrs = groupInfos[groupName];
        const size = groupArrs.length;
        const lastGroup = groupArrs[size - 1];
        const insertNode = lastGroup[lastGroup.length - 1].node.nextSibling;
        groupInfos[groupName].push([]);
        groupInstances[groupName].push([]);
        addCellInfos.forEach(item => {
            addItemByAttrib(item[0], wordItemStyle(item), value, insertNode, size);
        })
        itemChange();
    };
    const deleteItemById = (id: number) => {
        let hasRemove = false;
        const size = cellInfos.length - 1;
        for (let i = 0; i < cellInfos.length; i += 1) {
            if (id === cellInfos[i].id) {
                hasRemove = true;
                container.removeChild(cellInfos[i].node);
            }
            if (hasRemove && i < size) {
                cellInfos[i] = cellInfos[i + 1];
            }
        }
        if (hasRemove) {
            cellInfos.pop();
        }
    }
    /**
     * 通过组名删除组成员
     * @param {*} groupName 
     * @param {*} rows 
     */
    this.delCellsByGroupName = (groupName) => {
        const arrs = groupInfos[groupName].pop();
        groupInstances[groupName].pop();
        arrs.forEach((item: any) => {
            deleteItemById(item.id);
        });
        itemChange();
    };
    this.findGroupCells = (groupName) => {
        return groupInfos[groupName];
    }

}

export default DocContainer as unknown as DocContainerType;